
#include "nane/FileArchiveSystem/ZIP/ZIPFileDeflated.h"

namespace nane
{
    ////////////////////////////////////////////////////////////////////////////////////////////////
    static const uint32 c_maxInBufferSize = 128 * 1024;     // 128K
    static const uint32 c_skipBufferSize = 1024;            // 1K
    ////////////////////////////////////////////////////////////////////////////////////////////////
    ZIPFileDeflated::ZIPFileDeflated( ZIPFileArchive* _creator, const InputSeekablePtr& _input, uint32 _beginPos, uint32 _size, uint32 _uncompressedSize )
        : Disposable<ZIPFileDeflated, ZIPFileBase>( _creator, _input, _beginPos, _size )
        , m_uncompressedSize( _uncompressedSize )
        , m_currentFilePos( 0 )
        , m_initialized( false )
        , m_uncompressedPos( 0 )
    {
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    ZIPFileDeflated::~ZIPFileDeflated()
    {
        if( m_initialized == true )
        {
            Shutdown();
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool ZIPFileDeflated::Initialize()
    {
        m_zStream.zalloc = Z_NULL;
        m_zStream.zfree = Z_NULL;
        m_zStream.opaque = Z_NULL;
        m_zStream.avail_in = 0;
        m_zStream.next_in = Z_NULL;
        int ret = ::inflateInit2(&m_zStream, -15);  // skip header and integrity check
        if (ret != Z_OK)
        {
            return false;
        }
        m_inBuffer.resize(std::min(m_size, c_maxInBufferSize));
        m_initialized = true;
        return true;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void ZIPFileDeflated::Shutdown()
    {
        ::inflateEnd(&m_zStream);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    uint32 ZIPFileDeflated::Read( byte* _buffer, uint32 _count )
    {
        uint32 uncompressedBytesRead = 0;
        uint32 toUncompress = std::min(_count, m_uncompressedSize - m_uncompressedPos);
        m_zStream.avail_out = toUncompress;
        m_zStream.next_out = _buffer;
        int flush = (m_zStream.avail_out == m_uncompressedSize) && (m_inBuffer.size() == m_size) ? Z_FINISH : Z_NO_FLUSH;
        while( m_zStream.avail_out != 0 )
        {
            if( m_zStream.avail_in == 0 )
            {
                interface->Seek(m_currentFilePos + m_beginPos);
                m_inBufferSize = interface->Read(&m_inBuffer[0], m_inBuffer.size());
                m_currentFilePos += m_inBufferSize;
                m_zStream.avail_in = m_inBufferSize;
                m_zStream.next_in = (&m_inBuffer[0]);
            }
            int result = ::inflate(&m_zStream, flush);
            if( result != Z_STREAM_END && result != Z_OK )
            {
                break;
            }
        }
        uncompressedBytesRead = toUncompress - m_zStream.avail_out;
        m_uncompressedPos += uncompressedBytesRead;
        return uncompressedBytesRead;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void ZIPFileDeflated::Seek( uint32 _pos )
    {
        uint32 seekPos = std::min(_pos, m_uncompressedSize);
        if( seekPos > m_uncompressedPos )   // seek forward
        {
            uint32 toSkip = seekPos - m_uncompressedPos;
            byte skipBuffer[c_skipBufferSize];
            while( toSkip != 0 )
            {
                uint32 toRead = std::min(toSkip, c_skipBufferSize);
                uint32 bytesRead = Read(skipBuffer, toRead);
                if( bytesRead != toRead )
                {
                    break;
                }
                toSkip -= bytesRead;
            }
        }
        else if( seekPos < m_uncompressedPos )  // seek backwards
        {
            // reset stream and seek forward =)
            m_uncompressedPos = 0;
            m_currentFilePos = 0;
            Seek(seekPos);
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    uint32 ZIPFileDeflated::Tell()
    {
        return m_uncompressedPos;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    uint32 ZIPFileDeflated::Size() const
    {
        return m_uncompressedSize;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
}   // namespace nane
